#include "parser.h"
#include "seive.h"
#include <mpi.h>


void distributed_phrase(int argc, char *argv[])
{
    int length;
    char *text = fillBuffer(&length,"shakespe.txt");
    int numprocs, rank;
    MPI_Status stat;
    int *limits;
    int countWord = 0;
    int totalWordCount= 0;
    int countPhrase = 0;
    int totalPhraseCount= 0;
    int i,j;
    long long int maxPrimes;
    long long int maxPalindromes;
    struct runTime rt;    
    long long int palindromeTimeElapsed;
    long long int maxPalindromePhrase;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);


    rt.palindromes = 0;
    rt.primes = 0;
    limits = malloc((numprocs) * sizeof(int));

    if(rank == 0)
    {

        limits[numprocs-1] = length;

        for(i = 0; i < numprocs-1; i++)
        {
            limits[i] = (length/numprocs) * (i+1);
            for(j = limits[i]; j < length && !isTermCh(text[j]); j++)
            {
                limits[i]++;
            }
            limits[i]++;
        }
    }

    /*Transmit data to other hosts*/
    MPI_Bcast(limits,numprocs,MPI_INT,0,MPI_COMM_WORLD);
    MPI_Bcast(text,length,MPI_CHAR,0,MPI_COMM_WORLD);

    omp_set_num_threads(2);
    
    if(rank == 0)
    {

        #pragma omp parallel
        {
            #pragma omp sections
            {

                #pragma omp section
                countWord = parser(text,0,limits[rank],&rt);

                #pragma omp section
                countPhrase = parserPhrase(text,0,limits[rank],&palindromeTimeElapsed);
            }
        }
        
    }
    else
    {
        #pragma omp parallel
        {
            #pragma omp sections
            {

                #pragma omp section
                countWord = parser(text,limits[rank-1],limits[rank],&rt);

                #pragma omp section
                countPhrase = parserPhrase(text,limits[rank-1],limits[rank],&palindromeTimeElapsed);
            }
        }
    }

    /*Get Palindromes count*/
    MPI_Reduce(&countWord,&totalWordCount,1,MPI_INT,MPI_SUM,0,MPI_COMM_WORLD);
    MPI_Reduce(&countPhrase,&totalPhraseCount,1,MPI_INT,MPI_SUM,0,MPI_COMM_WORLD);

    /*Get Max time for each case*/
    MPI_Reduce(&palindromeTimeElapsed,&maxPalindromePhrase,1,MPI_LONG_LONG_INT,MPI_MAX,0,MPI_COMM_WORLD);
    MPI_Reduce(&rt.primes,&maxPrimes,1,MPI_LONG_LONG_INT,MPI_MAX,0,MPI_COMM_WORLD);
    MPI_Reduce(&rt.palindromes,&maxPalindromes,1,MPI_LONG_LONG_INT,MPI_MAX,0,MPI_COMM_WORLD);

    /*Print Results*/
    if(rank == 0)
    {

        double primes = maxPrimes/(double)1000000;
        double palindromeWords= maxPalindromes/(double)1000000;
        double palindromePhrase = maxPalindromePhrase/(double)1000000;

        printf("Palindrome Words: %d\n",totalWordCount);
        printf("Palindrome Phrases: %d\n",totalPhraseCount);
        printf("Max. Time to prime: %lf\n",primes);
        printf("Max. Time for word: %lf\n",palindromeWords);
        printf("Max. Time for phrase: %lf\n",palindromePhrase);
    }

    MPI_Finalize();
}


void distributed_word(int argc, char* argv[])
{
	int length;
	char *text = fillBuffer(&length,"wikipedia.txt");
	int numprocs, rank;
   	MPI_Status stat;
   	int *limits;
    int count = 0;
    int totalCount= 0;
    int i,j;
    long long int maxPrimes;
    long long int maxPalindromes;
    struct runTime rt;

    rt.palindromes = 0;
    rt.primes = 0;

   	MPI_Init(&argc, &argv);
   	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
   	MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    limits = malloc((numprocs) * sizeof(int));

   	if(rank == 0)
   	{

		limits[numprocs-1] = length;

    	for(i = 0; i < numprocs-1; i++)
    	{
    		limits[i] = (length/numprocs) * (i+1);
    		for(j = limits[i]; j < length && !isTermCh(text[j]); j++)
    		{
    			limits[i]++;
    		}
    		limits[i]++;
    	}
	}

    MPI_Bcast(limits,numprocs,MPI_INT,0,MPI_COMM_WORLD);
    MPI_Bcast(text,length,MPI_CHAR,0,MPI_COMM_WORLD);

    if(rank == 0)
        count = parser(text,0,limits[rank],&rt);
    else
        count = parser(text,limits[rank-1],limits[rank],&rt);

	MPI_Reduce(&count,&totalCount,1,MPI_INT,MPI_SUM,0,MPI_COMM_WORLD);
    MPI_Reduce(&rt.primes,&maxPrimes,1,MPI_DOUBLE,MPI_MAX,0,MPI_COMM_WORLD);
    MPI_Reduce(&rt.palindromes,&maxPalindromes,1,MPI_LONG_LONG_INT,MPI_MAX,0,MPI_COMM_WORLD);


    if(rank == 0)
    {
        double primes = maxPrimes/(double)1000000;
        double palindromes = maxPalindromes/(double)1000000;

        printf("Palindrome words: %d\n", totalCount);
        printf("Max. Time to prime: %lf\n",primes);
        printf("Max. Time for words: %lf\n",palindromes);
    }

	MPI_Finalize();
}



static int parser(char *buffer, int begin, int length, struct runTime *rt)
{
	int i;
	unsigned int end = 0;
    int word_count = 0;
    long long int temp;

    struct timeval begin_pr;
    struct timeval end_pr;
    struct timeval begin_pa;
    struct timeval end_pa;
    struct timeval diff_pa;
    struct timeval diff_pr;

	if(buffer == NULL)
		return 1;

    gettimeofday(&begin_pa,NULL);

	do
	{
		while(!isAlpha(buffer[begin]) && begin < length)
		{
			begin++;
			end = begin;
		}

		if(!isAlpha(buffer[end]) && buffer[end] != '-')
		{
			if(end - begin > 1)
            {
				if(processWord(buffer,begin,end-1) == 1)
                {
					word_count++;

                    gettimeofday(&begin_pr,NULL);
                    seive(sumCh(buffer,begin,end-1));
                    gettimeofday(&end_pr,NULL);

                    time_sub(&diff_pr,&end_pr,&begin_pr);

                    temp = tolld(&diff_pr);

                    if(rt->primes < temp)
                      rt->primes = temp;
                }
			}
			begin = end + 1;
		}
		end++;
	} while(end < length);

    gettimeofday(&end_pa,NULL);;

    time_sub(&diff_pa,&end_pa,&begin_pa);
    rt->palindromes = tolld(&diff_pa) - rt->primes;
	
	return word_count;
}

char * fillBuffer(int *length, char *fileName)
{
	FILE *fp;
	char *buffer = NULL;
	char ch;

	if((fp = fopen(fileName,"r")) == NULL)
		return NULL;

	fseek(fp,0,SEEK_END);
	*length = ftell(fp)/sizeof(char);
	(*length)++;
	fseek(fp,0,SEEK_SET);
	buffer = malloc((*length) * sizeof(char));
	fread(buffer,sizeof(char),--(*length),fp);
	buffer[*length] = '\0';
	fclose(fp);
	return buffer;
}


int processWord(char *buffer, int begin, int end)
{
	int i;
	int j = end;
	
	i=begin;
	while(i < j)
	{
		if(toUpperCase(buffer[i]) == toUpperCase(buffer[j]))
		{
			i++;
			j--;
		}
		else
		{
			return 0;
		}
	}
	return 1;
}



int parserPhrase(char *buffer, int begin, int length, long long int * timeElapsed)
{
    int i;
    unsigned int end = begin;
    int phrase_count = 0;
    int p_count = 0;
    struct timeval begin_pa;
    struct timeval end_pa;
    struct timeval diff;

    if(buffer == NULL)
        return 1;

    gettimeofday(&begin_pa,NULL);

    do
    {
        if(isTermCh(buffer[end]))
        {
            if(end - begin > 1)
            {
                phrase_count++;
                p_count = p_count + processPhrase(buffer,begin,end-1);
                begin = end + 1;
            }
        }
        end++;

    } while(end < length);


    gettimeofday(&end_pa,NULL);
    time_sub(&end_pa,&end_pa,&begin_pa);
    *timeElapsed = tolld(&end_pa);
  
    return p_count;
}

int processPhrase(char *buffer, int begin, int end)
{
    int i = begin;
    int j = end;
    int contLetters = 0;

    while(i < j)
    {
        if(isLetter(buffer[i]) || isNumber(buffer[i] ))
        {
            if(isLetter(buffer[j]) || isNumber(buffer[j]) )
            {
                if(toUpperCase(buffer[i]) == toUpperCase(buffer[j]))
                {
                    i++;
                    j--;
                    contLetters++;
                }
                else return 0;
            }
            else j--;
        }
        else i++;
    }

    if(contLetters < 1) return 0;

    return 1;
}


static int isLetter(char ch)
{
	if(ch >= 65 && ch <= 122)
	{
		if(ch <= 90 || ch >= 97)
			return 1;
		else
			return 0;
	}
	return 0;
}

static int isAlpha(char ch)
{
	if(isLetter(ch))
		return 1;
	else if(isNumber(ch))
		return 1;
	else return 0;
}

static int isNumber(char ch)
{
	if(ch >= 48 && ch <= 57)
		return 1;
	else return 0;
}

static char toUpperCase(char ch)
{
	if(isNumber(ch))
		return ch;

	if(ch >= 97)
		return ch - 32;
	else
		return ch;
}


int isTermCh(char ch)
{
    if ( (ch == '.') || (ch == '!') || (ch == '?') )
        return 1;

    return 0;
}


int sumCh(char *buffer, int begin, int end)
{
    int i;
    int count_ch = 0;

    for(i = begin; i<=end; i++)
    {
        if(isAlpha(buffer[i]))
            count_ch += buffer[i];
    }

    return count_ch;
}

void time_sub(struct timeval *result, struct timeval *t2, struct timeval *t1 )
{
    long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec);
    result->tv_sec = diff/1000000;
    result->tv_usec = diff%1000000;
}

long long int tolld(struct timeval * tod)
{
    long long t1;

    t1 =  tod->tv_sec *1000000 + tod->tv_usec;

    return t1;
}

